#pragma once

#include <vector>
#include <algorithm>

using namespace std;

#include <Glut/glut.h>

#include "Vector3.h"
#include "Quaternion.h"
#include "Matrix33.h"
#include "aabb.h"

#include "TriVertex.h"
#include "TriEdge.h"
#include "TriFace.h"



//////////////////////////////////////////////////////////////////////////////
// TriFaceRender
//
// Description:
//      Rendering properties for a face.
//////////////////////////////////////////////////////////////////////////////

class TriFaceRender
{

public:
   GLfloat *textCoords;

   GLuint *solidColor;
   GLfloat *smoothColor;
   GLfloat *smoothColorInvert;

   bool vertexNormals;
   bool faceNormal;
   bool invertNormal;

   bool vertexColors;
   bool faceColors;
   GLfloat maxVal;
   GLfloat minVal;

   GLfloat shininess;

   bool edges;

public:
   //Constructors and destructors
   TriFaceRender(void)
   {
      textCoords=NULL;
      solidColor=NULL;
      smoothColor=NULL;
      smoothColorInvert=NULL;
      vertexNormals=false;
      faceNormal=false;
      invertNormal=false;
      vertexColors=false;
      faceColors=false;
      edges=false;
      shininess = 30.0f;
   }

   ~TriFaceRender(void){}

};

//////////////////////////////////////////////////////////////////////////////
// TriMesh
//
// Description:
//      Triangular mesh class.
//////////////////////////////////////////////////////////////////////////////
class TriMesh
{

public:
   typedef enum {UNKNOWN, TRI, OFF, OBJ, DCMESH, POV, PAR, SMF, OBJTEXFACE} FileType;

   static bool subdivisionLoop;

    int numReferences;
protected:

   // Data members
   vector<TriVertex*> verts;
   vector<TriFace*> faces;
   vector<TriEdge*> edges;

   //Flags that indicate the state of the mesh
   bool connected;
   bool geomComputed;
   bool useUV; //Use texture vertices.
   string textureMaterialName;

   //Flag that indicate if texture coordinates are stored in the faces or in the vertices.
   bool UvsInFaces;
   
   //Geometry
   Vector3 center;
   AABB bb;
   Real radius;
   Real volume;
   Real area;
   Matrix33 inertia;

   //Callback for position precomputations
   void(*PreComputePositionsCallback)(void);

   //Display
   Vector3 color;
   TriFaceRender faceProperties;
    int texId;
   bool displayHandle;

   bool quickTriMesh;

   double SumDensity;
   vector<double> cdf;

public:

   TriMesh(void)
   {
      Initialize();
   }

   //TriMesh(const Real* vs, const int* fs, int vn, int fn, Real scalefactor);
   TriMesh(const Real* vs,
           const int* fs,
           int vn,
           int fn,
           Real scalefactor = 1.0f,
           const Real* ts=NULL,
           int tn = 0,
           const int *tis=NULL,
           bool storeUvsInFaces = false,
           bool connect = true);
   TriMesh(TriMesh* mesh);

   ~TriMesh(void);

   static TriMesh* CreatePatchMesh(int numX, int numY, Real size);
   static TriMesh* CreateBlockMesh(const Vector3& min, const Vector3& max);
   static TriMesh* ReadFromFile(const char* name, FileType type, Real scale = 1.0f);
   static bool ReadFromFile(const char* name, FileType type, Real scale, Real*& vs, int vn);
   static void ReadFromFile(vector<TriMesh*>& meshes, const char* name, FileType type, Real scale);

   void PrintToFile(const char* name, FileType type, Real scale = 1.0, const char *meshName = "a_mesh");
   void PrintToFile(ofstream& fout, FileType type, Real scale = 1.0, const char *meshName = "a_mesh");
   void PrintToFileFacesNotMarked(const char *name, FileType type, Real scale, const char *meshName);

   void Initialize(void);

   // Get functions
   vector<TriVertex*>& Vertices(void)
   {
      return verts;
   }

   const vector<TriVertex*>& Vertices(void) const
   {
      return verts;
   }

   void GetInnerRingVertices(Real threshold, vector<TriVertex*>& innerRing);

   vector<TriEdge*>& Edges(void)
   {
      return edges;
   }

   const vector<TriEdge*>& Edges(void) const
   {
      return edges;
   }

   Real GetAverageEdgeLenght (void) const;

   vector<TriEdge*>* GetUndirectedEdges(void);

   vector<int>* GetUndirectedEdgesInds(void);

   vector<TriEdge*>* GetSecondLevelEdges(void);

   vector<TriEdge*>* GetFlipEdges(void);

   vector<TriFace*>& Faces(void)
   {
      return faces;
   }

   const vector<TriFace*>& Faces(void) const
   {
      return faces;
   }

    unsigned int NumFaces(void)
    {
        return (unsigned int) faces.size();
    }

    unsigned int NumVertices(void)
    {
        return (unsigned int) verts.size();
    }

   // State of the mesh
   bool Connected(void) const
   {
      return connected;
   }

   bool GeomComputed(void) const
   {
      return geomComputed;
   }

   bool UsedUVTexture(void) const { return useUV; }

   // TriMesh setup functions
   //void Store(const Real* vs, const int* fs, int vn, int fn, Real scalefactor);

    /**
        Store: Creates a TriMesh object from a list of buffers.
    
        @param filename [IN]: Filename of the texture.
        @param vs [IN]: Buffer which contains all the vertices of the mesh, each vertex is built on three Reals.
        @param fs [IN]: Buffer which contains all the faces of the mesh, each face is built on three indexes (int).
        @param vn [IN]: Number of vertices that has the "vs" buffer.
        @param fn [IN]: Number of faces that has the "fs" buffer.
        @param scalefactor [IN]: Scales the size of the mesh.
        @param ts [IN]: Buffer which contains all the texture coordinates (u,v) of the mesh.
        @param tn [IN]: Number of texture coordinates has the buffer "ts".
        @param tis [IN]: Buffer which contains all the texture indexes to the ts buffer, three per face.
    */
   void Store(const Real* vs, const int* fs, int vn, int fn, Real scalefactor=1.0f, const Real* ts=NULL, int tn = 0, const int *tis=NULL);
   void QuickStore(const Real* vs, const int* fs, int vn, int fn);

   void Quicksort(TriEdge** es, int p, int r);

   bool Connect(void);

   void Append(TriMesh *mesh);

   //bool Create(const Real* vs, const int* fs, int vn, int fn, Real scalefactor);
   bool Create(const Real* vs, const int* fs, int vn, int fn, Real scalefactor, const Real* ts=NULL, int tn = 0, const int *tis=NULL);
   bool CreateWithoutConnect(const Real* vs, const int* fs, int vn, int fn);

   void CheckBoundary(void);

   void CheckDegeneracies(void);

   void CopyPositions(TriMesh *other);

   // Debug functions
#ifdef _DEBUG
   bool VerifyTopology(void);

   bool VerifyGeometry(void);
#endif

   // Geometry functions
   void ComputeGeometry(void);

   void ComputeEdgeFaceGeometry(void);

   void ComputeEdgeGeometry(void);

   void ComputeVertexGeometry(void);

   void ComputePositions(void);

   void ComputeNormals(void);

   void ComputeFaceNormals(void);

   void ComputeVertexNormals(void);

   void ComputeVertexAreas(void);

   void UpdateOldPositions(void);

   void ComputeCenter(void);

   void ComputeRadius(void);

   void ComputeVolume(void);

   void ComputeArea(void);

   void ComputeInertia(void);

   void ComputeBoundingBox(void);

   void ComputeOwners(void);

   void ComputeFeatures(void);

   void CalculateRotationMatrix2D(void);

   const Vector3& Center(void) const
   {
      return center;
   }

   Real Radius(void) const
   {
      return radius;
   }

   Real Volume(void) const
   {
      return volume;
   }

   Real Area(void) const
   {
      return area;
   }

   const Matrix33& Inertia(void) const
   {
      return inertia;
   }

   const AABB& BBox(void) const
   {
      return bb;
   }

   Vector3 TranslateToCenter(void);
   Vector3 TranslateToBBCenter(void);

   void Transform(const Vector3& pos, const Quaternion& rot);
   void TransformTemporary(const Vector3& pos, const Quaternion& rot);
   

    Vector3 Scale(Real scale)
    {
        Matrix33 m = Matrix33::ZERO;
        Vector3 vs;
        vs.setValue(scale);
        m.setDiagonal(vs);

        return Scale(m);
    }

    Vector3 Scale(const Matrix33& scale);

   void ScaleWRTPoint(Real scale, const Vector3& point)
   {
        Matrix33 m = Matrix33::ZERO;
        Vector3 vs;
        vs.setValue(scale);
        m.setDiagonal(vs);

        ScaleWRTPoint(m, point);
   }

   void ScaleWRTPoint(const Matrix33& scale, const Vector3& point);

    void Translate(const Real x, const Real y, const Real z);

    void Enlarge(Real scale);


   //Display
   void Draw(void);
   void DrawWithOptions(void);
   void SetDisplayHandle(bool set) { displayHandle = set; };
   bool GetDisplayHandle(void) { return displayHandle; };

   TriFaceRender *GetFaceProperties(void) {return &faceProperties;};

    TriMesh* Copy()
    {
        return new TriMesh(this);
    }


   void sortFaces();


   void DisconnectAtFeatures(void);

   vector<vector<TriVertex*> >* GetClusters(int nClusters) const;

    void SetTextureId(int id)
    {
        texId = id;
    }

    int GetTextureId()
    {
        return texId;
    }

    vector<TriVertex *>* getTriVertexInBB (const AABB &bb, Real tolerance=0.001f, bool useX=true, bool useY=true, bool useZ=true) const;
    vector<TriVertex *>* getTriVertexOnTop(Real fract);
    vector<TriVertex *>* getTriVertexOnBottom(Real fract);

    void setPreComputeFunctionCALLBACK (void(*callback_function)(void)) 
    {
        this->PreComputePositionsCallback = callback_function;
    }

#ifndef _WIN32_
    bool loadTexture(string filename);
    void unloadTexture();
#endif //_WIN32_

    void setUVcoordinateFromOBJ(TriMesh *tmOBJ);
    void setTextureMaterialName(string name) { textureMaterialName = name; }
    string getTextureMaterialName() { return textureMaterialName; }
    bool AreEqual (TriMesh *mesh1, TriMesh *mesh2);

    void CalculateCDF( void);
    int getNodePosition( double cdf);
    void EmptyCDF(void) {cdf.clear();}

    void SetToOriginalPositionAndOrientation(void)
    {
       for(int i=0;i<Vertices().size();++i)
       {
          const Vector3 &p = Vertices()[i]->PositionInit();
          Vertices()[i]->Position(p[0],p[1],p[2]);
       }
    }

};



